use std::vec;

use pest::Parser;
use pest::error::LineColLocation;
use pest_derive::Parser;

#[derive(Parser)]
#[grammar = "sysy.pest"]
pub struct SysyParser;

#[allow(dead_code)]
#[derive(Debug)]
pub struct GrammarUnit {
    pub rule: String,
    pub value: String,
    pub line_num: usize,
}

#[allow(dead_code)]
pub fn tokenize(input: &str) -> Vec<GrammarUnit> {
    let parse_result = SysyParser::parse(Rule::PROGRAM, input);
    let mut tokens: Vec<GrammarUnit> = vec![];
    match parse_result {
        Ok(pairs) => {
            let inner_pairs = pairs.flat_map(|pair| pair.into_inner());
            for pair in inner_pairs {
                if pair.as_rule() == Rule::TOKEN_INVALID {
                    let ch = pair.as_str();
                    tokens.push(GrammarUnit {
                        rule: "ERROR".to_string(),
                        value: format!("Mysterious character \"{}\".", ch),
                        line_num: pair.line_col().0,
                    });
                    continue;
                }
                let inner_inner_pairs: Vec<GrammarUnit> = pair
                    .into_inner()
                    .map(|p| match p.as_rule() {
                        Rule::INTEGER_CONST => GrammarUnit {
                            rule: format!("{:?}", p.as_rule()),
                            value: p.as_str().to_string(),
                            line_num: p.line_col().0,
                        },
                        Rule::RETURN
                        | Rule::IF
                        | Rule::INT
                        | Rule::CONST
                        | Rule::VOID
                        | Rule::ELSE
                        | Rule::WHILE
                        | Rule::BREAK
                        | Rule::CONTINUE
                        | Rule::L_BRACE
                        | Rule::R_BRACE
                        | Rule::L_PAREN
                        | Rule::R_PAREN
                        | Rule::ASSIGN
                        | Rule::SEMICOLON
                        | Rule::MINUS
                        | Rule::NOT
                        | Rule::COMMA
                        | Rule::PLUS
                        | Rule::MUL
                        | Rule::MOD
                        | Rule::DIV
                        | Rule::EQ
                        | Rule::NEQ
                        | Rule::LE
                        | Rule::GE
                        | Rule::LT
                        | Rule::GT
                        | Rule::AND
                        | Rule::OR
                        | Rule::L_BRACKT
                        | Rule::R_BRACKT
                        | Rule::IDENT => GrammarUnit {
                            rule: format!("{:?}", p.as_rule()),
                            value: p.as_str().to_string(),
                            line_num: p.line_col().0,
                        },
                        _ => {
                            println!(
                                "extra {:?}, {} at line {:?}",
                                p.as_rule(),
                                p.as_str(),
                                p.line_col()
                            );
                            unreachable!()
                        }
                    })
                    .collect();
                tokens.extend(inner_inner_pairs);
            }
            tokens
        }
        Err(e) => {
            if let LineColLocation::Pos((line, col)) = e.line_col {
                let line_str = e.line();
                let ch = line_str.chars().nth(col.saturating_sub(1) as usize);
                let msg = match ch {
                    Some(c) => format!("Mysterious character \"{}\".", c),
                    None => "Mysterious character \"\".".to_string(),
                };
                vec![GrammarUnit {
                    rule: "ERROR".to_string(),
                    value: msg,
                    line_num: line,
                }]
            } else {
                eprintln!("Error: {}", e);
                vec![]
            }
        }
    }
}

#[allow(dead_code)]
pub fn grammer_units_to_results(grammer_units: Vec<GrammarUnit>) -> Vec<String> {
    let mut outputs = vec![];
    let has_error = grammer_units.iter().any(|t| t.rule == "ERROR");
    for token in grammer_units {
        if token.rule == "ERROR" {
            outputs.push(format!(
                "Error type A at Line {}: {}",
                token.line_num, token.value
            ));
        } else if !has_error {
            if token.rule == "INTEGER_CONST" {
                outputs.push(format!(
                    "{} {} at Line {}.",
                    token.rule,
                    int_conversion(token.value),
                    token.line_num
                ));
            } else {
                outputs.push(format!(
                    "{} {} at Line {}.",
                    token.rule, token.value, token.line_num
                ));
            }
        }
    }
    outputs
}

#[allow(dead_code)]
fn int_conversion(s: String) -> i32 {
    let abs_s = s;
    let abs_value = if abs_s.starts_with("0x") || abs_s.starts_with("0X") {
        i32::from_str_radix(&abs_s[2..], 16)
    } else if abs_s.starts_with('0') && abs_s.len() > 1 {
        i32::from_str_radix(&abs_s[1..], 8)
    } else {
        i32::from_str_radix(&abs_s, 10)
    };
    abs_value.unwrap()
}
